package com.cruise.game.common.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import io.jsonwebtoken.ExpiredJwtException;

/**
 * JWT工具类
 *
 * @author cruise
 * @date 2025-02-28
 */
@Slf4j
@Component
public class JwtUtils {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private long expiration;

    @Value("${jwt.header}")
    private String header;

    @Value("${jwt.tokenPrefix}")
    private String tokenPrefix;

    /**
     * 从请求中获取Token
     *
     * @param request 请求
     * @return Token
     */
    public String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(header);
        if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(tokenPrefix)) {
            // 确保带有一个空格，并去除前缀后的所有前后空格
            return bearerToken.substring(tokenPrefix.length()).trim();
        }
        return null;
    }

    /**
     * 生成Token
     *
     * @param userId 用户ID
     * @param role   用户角色
     * @return Token
     */
    public String generateToken(Long userId, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("role", role.toLowerCase());
        return generateToken(claims);
    }

    /**
     * 生成Token
     *
     * @param claims 数据声明
     * @return Token
     */
    private String generateToken(Map<String, Object> claims) {
        Date createdDate = new Date();
        Date expirationDate = new Date(createdDate.getTime() + expiration);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 获取签名密钥
     *
     * @return 密钥
     */
    private Key getSigningKey() {
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 从Token中获取数据声明
     *
     * @param token Token
     * @return 数据声明
     * @throws ExpiredJwtException 如果令牌已过期
     */
    public Claims getAllClaimsFromToken(String token) throws ExpiredJwtException {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 从Token中获取用户ID
     *
     * @param token Token
     * @return 用户ID
     * @throws ExpiredJwtException 如果令牌已过期
     */
    public Long getUserIdFromToken(String token) throws ExpiredJwtException {
        return Long.valueOf(getAllClaimsFromToken(token).get("userId").toString());
    }

    /**
     * 从Token中获取用户角色
     *
     * @param token Token
     * @return 用户角色
     * @throws ExpiredJwtException 如果令牌已过期
     */
    public String getUserRoleFromToken(String token) throws ExpiredJwtException {
        return getAllClaimsFromToken(token).get("role").toString();
    }

    /**
     * 从Token中获取过期时间
     *
     * @param token Token
     * @return 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    /**
     * 从Token中获取指定数据声明
     *
     * @param token          Token
     * @param claimsResolver 数据声明解析器
     * @param <T>            返回值类型
     * @return 数据声明值
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    /**
     * 判断Token是否过期
     *
     * @param token Token
     * @return 是否过期
     */
    public Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 刷新Token
     *
     * @param token Token
     * @return 刷新后的Token
     */
    public String refreshToken(String token) {
        final Claims claims = getAllClaimsFromToken(token);
        return generateToken(claims);
    }

    /**
     * 验证Token是否有效
     *
     * @param token       Token
     * @param userDetails 用户详情
     * @return 是否有效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    /**
     * 从Token中获取用户名
     *
     * @param token Token
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
}